using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Microsoft.Phone.Shell;
using Phoenix.Utilities;

namespace Phoenix
{
    class ApplicationBarBinder
    {
        readonly Dictionary<Type, ModelInfo> modelInfoLookup = new Dictionary<Type, ModelInfo>();
        private static readonly Regex tokenRegex = new Regex(@"\$(?<variableName>\w+)\$");

        public void BindApplicationBar(IApplicationBar applicationBar, ViewModelBase model)
        {
            foreach (var button in applicationBar.Buttons.OfType<ApplicationBarIconButton>())
            {
                BindNormalApplicationBarItemCommand(button, model);
            }
            foreach (var menuItem in applicationBar.MenuItems.OfType<ApplicationBarMenuItem>())
            {
                BindNormalApplicationBarItemCommand(menuItem, model);
            }
        }

        private void BindNormalApplicationBarItemCommand(IApplicationBarMenuItem applicationBarMenuItem, ViewModelBase model)
        {
            if (model == null)
                return;

            EnsureModelInfoGathered(model);

            var match = Regex.Match(applicationBarMenuItem.Text, @"(?<Text>.*?)\[(?<Command>.*?)\]");
            if (match.Success)
            {
                applicationBarMenuItem.Text = match.Groups["Text"].Value;
                BindNormalApplicationBarItemCommand(model, applicationBarMenuItem, match.Groups["Command"].Value);
            }
            else
            {
                BindNormalApplicationBarItemCommand(model, applicationBarMenuItem, applicationBarMenuItem.Text.Replace(" ", string.Empty));
            }

            var matchCollection = tokenRegex.Matches(applicationBarMenuItem.Text);
            if (matchCollection.Count > 0)
            {
                foreach (var variableName in from Match tokenMatch in matchCollection
                                             select tokenMatch.Groups["variableName"].Value)
                {
                    RegisterPropertyChange(model, applicationBarMenuItem, applicationBarMenuItem.Text, variableName);
                    BindText(model, applicationBarMenuItem, applicationBarMenuItem.Text, variableName);
                }
            }
        }

        //Must be static so we don't leak
        static void RegisterPropertyChange(ViewModelBase model, IApplicationBarMenuItem applicationBarMenuItem, string text, string variableName)
        {
            model.PropertyChanged += (sender, args) =>
                                     {
                                         if (args.PropertyName == variableName)
                                         {
                                             BindText(model, applicationBarMenuItem, text, variableName);
                                         }
                                     };
        }

        static void BindText(ViewModelBase model, IApplicationBarMenuItem applicationBarMenuItem, string text, string variableName)
        {
            var newValue = model.GetType().GetProperty(variableName).GetValue(model, null);
            if (newValue != null)
                applicationBarMenuItem.Text = text.Replace(string.Format("${0}$", variableName), newValue.ToString());
        }

        private void BindNormalApplicationBarItemCommand(ViewModelBase model, IApplicationBarMenuItem applicationBarMenuItem, string value)
        {
            var modelInfo = modelInfoLookup[model.GetType()];
            var property = modelInfo.ModelProperties.FirstOrDefault(k => k.Name == value || k.Name + "Command" == value);

            if (property != null)
            {
                var command = (ICommand)property.GetValue(model, null);
                if (command == null) return;
                command.CanExecuteChanged += (sender, e) => applicationBarMenuItem.IsEnabled = command.CanExecute(null);
                applicationBarMenuItem.Click += (sender, e) =>
                {
                    var focusObj = FocusManager.GetFocusedElement();

                    if (focusObj != null && focusObj is FrameworkElement)
                    {
                        var element = (FrameworkElement)focusObj;
                        var focusableElement = element.Ancestors().OfType<Control>().FirstOrDefault();
                        if (focusableElement != null)
                            focusableElement.Focus();
                    }
                    Deployment.Current.Dispatcher.BeginInvoke(() => command.Execute(null));
                };
                applicationBarMenuItem.IsEnabled = command.CanExecute(null);
                return;
            }

            var method = modelInfo.ModelMethods.FirstOrDefault(k => k.Name == value);
            if (method != null)
            {
                applicationBarMenuItem.Click += (sender, e) => method.Invoke(model, null);
            }
        }

        private void EnsureModelInfoGathered(ViewModelBase model)
        {
            var modelType = model.GetType();

            if (modelInfoLookup.ContainsKey(modelType)) return;

            var modelProperties = modelType
                .GetProperties(BindingFlags.Instance | BindingFlags.Public)
                .Where(p => typeof(ICommand).IsAssignableFrom(p.PropertyType));
            var modelMethods = modelType.GetMethods(BindingFlags.Instance | BindingFlags.Public)
                .Where(m => m.GetParameters().Length == 0);

            modelInfoLookup.Add(modelType, new ModelInfo(modelProperties, modelMethods));
        }
    }
}